<?php
	/**
	  * @package	MyDebuger
	  * @name		MyDebuger
	  * @brief		Clase para manejar depuracion y errores PHP
	  * @author		Hermann D. Schimpf <hschimpf@gschimpf.com>
	  * @license	http://opensource.org/licenses/gpl-2.0.php
	  * @date		2010-01-19
	  * @version	0.9.6
  	**/

	class MyDebuger extends HTMLFormater implements MyVersions {
		/* version de la clase */
		private static $version			= 0;
		/* subversion de la clase */
		private static $subversion		= 9;
		/* numero de revision de la clase */
		private static $revision		= 6;

		/* nombre de la instancia */
		private $name					= False;
		/* atributo con el ultimo error producido */
		private $lastError			= False;
		/* fichero de log para el registro de depuracion */
		private $logFile				= Null;
		/* cadena con el nombre del fichero log a comprimir */
		private $logFileCompress	= False;
		/* fichero lock para el fichero log */
		private $logFileLock			= False;
		/* bandera para mostrar el tiempo en los mensajes */
		private $saltoMsg				= False;
		/* bandera para des / habilitar la salida de depuracion */
		private $debugStat			= False;
		/* bandera para des / habilitar la salida de depuracion en formato HTML */
		private $html					= False;
		/* cadena para mensajes */
		private $cadenaShow			= '';
		/* atributo de tiempo de ejecucion actual */
		private $timeNow				= 0;
		/* atributo de tiempo de ejecucion inicial */
		private $timeStart			= 0;
		/* bandera para ver si se creo la instancia llamando a parent */
		private $created				= False;
		/* por alguna razon se posiciona en la raiz en algunos casos, almacenaremos la carpeta de trabajo de inicio */
		private $workingDir			= False;

		public function __construct($enableDebug = False, $name = __CLASS__, $maxExecutionTime = 120) {
			try {
				try {
					/* almacenamos la carpeta de trabajo */
					$this->workingDir = getcwd();
					/* verificamos si existe la clase necesaria para el funcionamiento */
					$this->verificarClase();
					/* modificamos la bandera */
					$this->created = True;
					/* evitamos que el script sea cancelado por el usuario */
					ignore_user_abort(True);
					/* asignamos el manejador de errores al metodo interno */
					set_error_handler(__CLASS__ . '::errorHandler', E_ALL);
					/* asignamos el tiempo maximo de ejecucion */
					set_time_limit($maxExecutionTime);
					/* almacenamos el tiempo de inicio */
					$this->saveTime();
					/* asignamos el nombre a la instancia */
					$this->name = $name;
					/* verificamos si habilitamos la depuracion */
					if ($enableDebug === True)
						/* habilitamos la depuracion */
						$this->enableDebug();
					/* habilitamos el fichero log */
					$this->enableLog();
					/* mostramos la version con la que estamos trabajando */
					$this->show('Version de la clase ' . (
						/* verificamos si la clase existe */
						class_exists($this->getName()) ?
						/* obtenemos la version */
						$this->blue(call_user_func(array($this->getName(), 'getVersion'))) :
						/* retornamos unknwon */
						$this->red('unknown')
					));
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$this->convertToMyException($e);
				}
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$this->show($e->getMensaje());
			}
		}

		public function __destruct() {
			try {
				try {
					/* verificamos si el archivo fichero log existe */
					if ($this->logFile !== Null) {
						/* finalizamos el log */
						$this->finalizarLog();
						/* comprimimos el fichero log */
						$this->comprimirLog();
						/* cambiamos el nombre a la clase */
						$this->name = __CLASS__;
						/* comprimimos nuestro log */
						$this->comprimirLog();
					}
					/* restauramos el manejador de errores */
					restore_error_handler();
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$this->newException($e);
				}
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$this->show($e->getMensaje());
			}
		}

		public static function getVersion($version = Null, $debug = False) {
			try {
				try {
					/* creamos una instancia */
					$getVersion = new MyDebuger($debug);
					/* verificamos si no especifico una version para comparar */
					if ($version === Null)
						/* retornamos la version actual de la clase */
						return 'v' . self::$version . '.' . self::$subversion . '.' . self::$revision;
					/* verificamos si especifico un numero */
					if (gettype($version) !== 'integer')
						/* salimos con una excepcion */
						$getVersion->newException('La version debe ser especificada en numeros');
					/* verificamos si la version es compatible */
					if ($version > ((self::$version * 100) + (self::$subversion * 10) + (self::$revision)))
						/* retornamos false */
						return False;
					/* retornamos true */
					return True;
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$getVersion->convertToMyException($e);
				}
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$getVersion->show($e->getMensaje());
			}
		}

		public final function getError($emptyOnGet = False, $whitOutMsg = False) {
			try {
				try {
					/* verificamos si se creo la instancia */
					$this->verificarInstancia();
					/* verificamos si es para utilizacion interna */
					if ($whitOutMsg === True)
						/* retornamos aqui el error para no mostrar mensajes */
						return $this->lastError;
					else
						/* almacenamos el error porque puede eliminarse mediante la bandera 'vaciar' */
						$errorReturn = $this->lastError;
					/* mostramos un mensaje */
					$this->show('Enviando ultimo error..', False);
					/* verificamos si se especifico vaciar */
					if ($emptyOnGet === True)
						/* eliminamos el ultimo error */
						$this->setError();
					/* mostramos OK al mensaje */
					$this->showOK();
					/* retornamos el error */
					return $errorReturn;
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$this->convertToMyException($e);
				}
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$this->show($e->getMensaje());
			}
		}

		private function enableLog() {
			try {
				try {
					/* verificamos si ya existe un fichero log abierto */
					if ($this->logFile !== Null)
						/* salimos con una excepcion */
						$this->newException('Ya existe un fichero log abierto');
					/* verificamos si existe la carpeta log */
					if (!file_exists('log')) {
						/* verificamos si podemos crear una carpeta */
						if (!is_writable('./'))
							/* salimos con una excepcion */
							$this->newException('La carpeta ' . $this->italic('log') . ' no existe y no se poseen permisos para crearla');
						/* creamos la carpeta */
						mkdir('./log');
					}
					/* habilitamos el log */
					$this->crearLogFile();
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$this->convertToMyException($e);
				}
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$this->show($e->getMensaje());
			}
		}

		public static final function errorHandler($errorNro, $errorMsg, $errorFile, $errorLine) {
			/* modificamos los errores de ejecucion a excepciones */
			throw new ErrorException($errorMsg, 0, $errorNro, $errorFile, $errorLine);
		}

		protected final function getTime() {
			try {
				try {
					/* verificamos si se creo la instancia */
					$this->verificarInstancia();
					/* obtenemos el tiempo actual */
					$timeInicio = explode(' ', microtime());
					/* retornamos la suma del tiempo actual */
					return $timeInicio[1] + $timeInicio[0];
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$this->convertToMyException($e);
				}
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$this->show($e->getMensaje());
			}
		}

		protected final function setError($stringError = False) {
			try {
				try {
					/* verificamos si se creo la instancia */
					$this->verificarInstancia();
					/* guardamos el error */
					$this->lastError = $stringError;
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$this->convertToMyException($e);
				}
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$this->show($e->getMensaje());
			}
		}

		protected final function show($stringMsg = Null, $break = True) {
			try {
				try {
					/* verificamos si se creo la instancia */
					$this->verificarInstancia();
					/* verificamos si especifico una cadena para mostrar */
					if ($stringMsg === Null)
						/* salimos con una excepcion */
						$this->newException('No se especifico la cadena a mostrar');
					/* almacenamos la cadena */
					$this->cadenaShow = $stringMsg;
					/* armamos la cadena de salida y la mostramos */
					$this->armarMostrarCadena($break);
					/* almacenamos la cadena en el log */
					$this->saveLog($stringMsg, $break);
					/* modificamos la bandera */
					$this->saltoMsg = True;
					/* verificamos si es un salto */
					if ($break === True)
						/* modificamos la bandera */
						$this->saltoMsg = False;
					/* vaciamos el buffer de salida */
					$this->vaciarBuffer();
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$this->convertToMyException($e);
				}
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$this->show($e->getMensaje());
			}
		}

		protected final function showOK($withColor = True) {
			try {
				try {
					/* verificamos si se creo la instancia */
					$this->verificarInstancia();
					/* verificamos si se paso un parametro correcto */
					if (gettype($withColor) !== 'boolean')
						/* salimos con una excepcion */
						$this->newException('El parametro debe ser booleano');
					/* mostramos la cadena OK */
					$this->show($withColor === True ? $this->green('OK') : 'OK', True);
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$this->convertToMyException($e);
				}
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$this->show($e->getMensaje());
			}
		}

		protected final function getTimeNow() {
			try {
				try {
					/* verificamos si se creo la instancia */
					$this->verificarInstancia();
					/* agregamos el tiempo transcurrido al tiempo actual */
					$this->timeNow = $this->getTime() - $this->timeStart;
					/* retornamos el tiempo con formato de 3 decimales y 6 digitos */
					return str_pad(number_format($this->timeNow, 3, ',', '.'), 7, '0', STR_PAD_LEFT);
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$this->convertToMyException($e);
				}
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$this->show($e->getMensaje());
			}
		}

		protected final function convertToMyException($e) {
			/* enviamos la excepcion desde MyException */
			$this->newException($e);
		}

		protected final function newException($e = False) {
			/* almacenamos el mensaje de la excepcion */
			$this->setError(
				/* verificamos si es un string */
				gettype($e) === 'string' ?
				/* almacenamos el string */
				$e :
				/* almacenamos el mensaje */
				$e->getMessage()
			);
			/* enviamos una excepcion para mostrar el mensaje */
			throw new MyException($e);
		}

		protected function verificarClase($className = 'MyException') {
			/* verificamos si existe */
			if (!class_exists($className)) {
				/* armamos el mensaje a mostrar */
				$mensajeError = 'Se necesita de la clase ' . $className . ' para utilizar la clase ' . ($this->getName() === False ? __CLASS__ : $this->getName());
				/* verificamos si existe la clase 'MyException' */
 				if (class_exists('MyException'))
					/* matamos el proceso con una excepcion */
 					$this->newException($mensajeError);
				/* si no existe matamos el proceso con un mensaje */
				die($mensajeError);
			}
		}

		protected function getDebugStatus() {
			/* retornamos el estado de la depuracion */
			return $this->debugStat;
		}

		private function enableDebug($enable = True) {
			/* habilitamos la salida */
			$this->debugStat = $enable;
			/* habilitamos el formateo HTML verificando si esta corriendo desde una consola o desde el navegador */
			$this->html = $this->runningFromBrowser();
		}

		private function runningFromBrowser() {
			/* retornamos si esta corriendo desde el navegador */
			return (
				/* verificamos si estan las variables de navegador */
				isset($_SERVER['HTTP_USER_AGENT']) || isset($_SERVER['HTTP_HOST']) ?
				/* retornamos True */
				True :
				/* retornamos false */
				False
			);
		}

		private function verificarInstancia() {
			/* verificamos si esta creado */
			if ($this->created === False && !isset($this->activeError)) {
				/* habilitamos la depuracion */
				$this->debugStat = True;
				/* creamos la bandera */
				$this->activeError = True;
				/* deshabilitamos el titulo */
				$this->saltoMsg = True;
				/* salimos con una excepcion */
				$this->newException('No se llamo al metodo constructor de la clase ' . __CLASS__);
			}
		}

		private function getName() {
			/* retornamos el nombre actual */
			return $this->name;
		}

		private function saveTime() {
			/* guardamos el tiempo de inicio */
			$this->timeStart = $this->getTime();
		}

		private function vaciarBuffer() {
			/* vaciamos el buffer de salida */
			flush();
		}

		private function armarMostrarCadena($break) {
			/* agregamos el titulo y tiempo a la cadena */
			$this->agregarTituloTiempo();
			/* formateamos la cadena con HTML */
			$this->formatearCadena($break);
			/* verificamos si esta habilitada la bandera de depuracion */
			if ($this->getDebugStatus() === True)
				/* mostramos la cadena de salida */
				echo (
					/* verificamos si esta habilitada la salida html */
					$this->html === True ?
					/* mostramos la cadena */
					$this->cadenaShow :
					/* si no esta habilitada, eliminamos el HTML */
					strip_tags($this->cadenaShow)
				);
		}

		private function agregarTituloTiempo() {
			/* almacenamos la cadena con el titulo y el tiempo actual */
			$this->cadenaShow = (
				/* verificamos si mostramos el nombre */
				$this->saltoMsg == False ?
				/* agregamos el tiempo y nombre de la clase */
				'[' . $this->getTimeNow() . ' ' . $this->getName() . '::debug] ' :
				/* no agregamos nada */
				''
			/* agregamos la cadena */
			) . $this->cleanHTML($this->cadenaShow);
		}

		private function formatearCadena($break) {
			/* formateamos la cadena dentro de la etiquera CODE */
			$this->cadenaShow = "<code>{$this->cadenaShow}</code>";
			/* agregamos el salto de linea a la cadena */
			$this->agregarSaltoLinea($break);
		}

		private function agregarSaltoLinea($break) {
			/* verificamos si esta habilitada la bandera de salto de linea */
			if ($break === True)
				/* agregamos el salto a la cadena */
				$this->cadenaShow .= "<br/>\n";
			/* si no, agregamos un espacio */
			else
				$this->cadenaShow .= ' ';
		}

		private function saveLog($stringText, $break, $showName = True) {
			try {
				try {
					/* verificamos si esta habilitado el log */
					if (gettype($this->logFile) === 'resource') {
						/* almacenamos el mensaje en el fichero log */
						fwrite($this->logFile, (
							/* verificamos si mostramos la cadena con el nombre */
							$showName === True && $this->saltoMsg === False ?
							/* agregamos el tiempo y nombre de la clase */
							'[' . $this->getTimeNow() . ' ' . $this->getName() . '::log] ' :
							/* no agregamos nada */
							''
						/* agregamos el mensaje sin las etiquetas html */
						) . strip_tags($this->cleanHTML($stringText)) . (
							/* verificamos si agregamos un salto */
							$break === True ?
							/* agregamos el salto */
							"\n" :
							/* agregamos un espacio */
							' '
						));
					}
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$this->convertToMyException($e);
				}
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$this->show($e->getMensaje());
			}
		}

		private function crearLogFile() {
			/* mostramos un mensaje */
			$this->show('Habilitando log en el fichero ' . $this->yellow($this->italic($this->getName() . '.log')) . '..', False);
			/* bloqueamos el fichero de log */
			$this->lockLogFile();
			/* creamos el fichero log */
			$this->createLogFile();
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function lockLogFile() {
			/* almacenamos el nombre del fichero lock */
			$this->logFileLock = 'log/.' . $this->getName() . '.log.lock~';
			/* verificamos si el fichero esta bloqueado */
			if (file_exists($this->logFileLock))
				/* salimos con una excepcion */
				$this->newException('El fichero log esta bloqueado');
			/* creamos el fichero lock */
			fclose(fopen($this->logFileLock, 'a'));
		}

		private function createLogFile() {
			/* habilitamos el fichero para almacenar el log */
			$this->logFile = fopen('log/' . $this->getName() . '.log', 'a+');
			/* iniciamos el fichero log */
			$this->saveLog("\n" . '----- ' . $this->getName() . '::log ' . date('Y-m-d H:i:s') . ' -----', True, False);
			/* habilitamos la bandera para que muestre el nombre */
			$this->saltoMsg = False;
			/* almacenamos el mensaje mostrado */
			$this->saveLog('Habilitando log en el fichero ' . $this->yellow($this->italic($this->getName() . '.log')) . '..', False);
			/* deshabilitamos la bandera */
			$this->saltoMsg = True;
		}

		private function finalizarLog() {
			/* mostramos un mensaje */
			$this->show('Eliminando bloqueo del fichero log..', False);
			/* nos posicionamos en la carpeta de trabajo */
			chdir($this->workingDir);
			/* verificamos si existe el fichero lock */
			if (file_exists($this->logFileLock)) {
				/* eliminamos el fichero lock del log */
				unlink($this->logFileLock);
				/* mostramos ok al mensaje */
				$this->showOK();
			} else
				/* mostramos un mensaje */
				$this->show($this->red('El fichero no existe'));
			/* mostramos un mensaje */
			$this->show('Finalizando fichero log');
			/* finalizamos el fichero log agregando un espacio vacio */
			$this->saveLog('', True, False);
			/* cerramos el fichero log */
			fclose($this->logFile);
			/* eliminamos el valor del atributo */
			$this->logFile = Null;
		}

		private function comprimirLog() {
			try {
				try {
					/* almacenamos el nombre del fichero con el que trabajaremos */
					$this->logFileCompress = 'log/' . $this->getName() . '.log';
					/* cambiamos el nombre a la instancia */
					$this->name = __CLASS__;
					/* almacenamos el tiempo actual */
					$this->saveTime();
					/* deshabilitamos la salida de depuracion */
					$this->enableDebug(False);
					/* iniciamos el log */
					$this->enableLog();
					/* verificamos que el fichero exista */
					if ($this->verificarExistenciaLog()) {
						/* verificamos el tamano del fichero log */
						if ($this->verificarTamano()) {
							/* comprimimos el fichero log */
							$this->comprimirFichero();
							/* asignamos el nombre para el fichero comprimido */
							$this->cambiarNombreComprimido();
							/* eliminamos el fichero log */
							$this->eliminarLog();
						}
					}
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$this->convertToMyException($e);
				}
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$this->show($e->getMensaje());
			}
			/* finalizamos el log */
			$this->finalizarLog();
		}

		private function verificarExistenciaLog() {
			/* mostramos un mensaje */
			$this->show('Verificando existencia del fichero ' . $this->yellow($this->italic(basename($this->logFileCompress))) . '..', False);
			/* verificamos si el fichero existe */
			if (!file_exists($this->logFileCompress)) {
				/* mostramos un mensaje */
				$this->show($this->reb('El fichero no existe'));
				/* retornamos False */
				return False;
			}
			/* mostramos ok al mensaje */
			$this->showOK();
			/* retornamos True */
			return True;
		}

		private function verificarTamano() {
			/* mostramos un mensaje */
			$this->show('Verificando tamano del fichero log..', False);
			/* verificamos si el tamano del fichero es mayor a 10 MB */
			if (filesize($this->logFileCompress) < (10 * 1024 * 1024)) {
				/* mostramos un mensaje */
				$this->show($this->red('El fichero aun no excede el limite'));
				/* retornamos False */
				return False;
			}
			/* mostramos ok al mensaje */
			$this->showOK();
			/* retornamos True */
			return True;
		}

		private function comprimirFichero() {
			/* mostramos un mensaje */
			$this->show('Comprimiendo el fichero log..', False);
			/* verificamos si podemos escribir en el directorio */
			if (!is_writable('log'))
				/* salimos con una excepcion */
				$this->newException('No se puede escribir en el directorio ' . $this->italic('log'));
			/* abrimos el fichero resultante */
			$logGzip = fopen($this->logFileCompress . '.gz', 'w');
			/* escribimos en el GZip todo el contenido comprimido del fichero log */
			fwrite($logGzip, gzencode(implode('', file($this->logFileCompress)), 9));
			/* cerramos el fichero GZip */
			fclose($logGzip);
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function cambiarNombreComprimido() {
			/* mostramos un mensaje */
			$this->show('Ordenando fichero comprimido..', False);
			/* iniciamos una variable temporal */
			$cant = 0;
			/* verificamos si el fichero existe */
			while (file_exists($this->logFileCompress . '.' . $cant . '.gz'))
				/* aumentamos un valor */
				$cant++;
			/* renombramos el fichero comprimido */
			rename($this->logFileCompress . '.gz', $this->logFileCompress . '.' . $cant . '.gz');
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function eliminarLog() {
			/* mostramos un mensaje */
			$this->show('Eliminando fichero log sin compresion..', False);
			/* eliminamos el fichero log */
			unlink($this->logFileCompress);
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		protected final function verificarMetodo($method) {
			/* verificamos si especifico el nombre del metodo */
			if ($method === Null)
				/* salimos con una excepcion */
				$this->newException('No se especifico el nombre del metodo a llamar');
			/* verificamos si la clase existe */
			if (!class_exists($this->getName()))
				/* salimos con una excepcion */
				$this->newException('La clase ' . $this->italic($this->getName()) . ' no existe');
			/* creamos una refleccion de la clase */
			$clase = new ReflectionClass($this->getName());
			/* recorremos los metodos */
			foreach ($clase->getMethods() AS $metodo) {
				/* verificamos si encontramos el metodo */
				if ($method == $metodo->name)
					/* retornamos true */
					return True;
			}
			/* si llegamos aqui salimos con una excepcion */
			$this->newException('El metodo ' . $this->italic($this->getName() . '::' . $method) . ' no existe');
		}

		protected final function buildArgs($args) {
			/* verificamos si hay parametros */
			if (count($args) === 0)
				/* retornamos Nulo */
				return Null;
			/* creamos una cadena */
			$arguments = '';
			/* recorremos los parametros */
			foreach ($this->getMethodArgs($args) AS $argument) {
				/* verificamos el tipo de dato */
				switch (strtolower(gettype($argument))) {
					/* si es booleano */
					case 'boolean':
						/* agregamos el parametro como texto False o True */
						$arguments .= ',' . ($argument === True ? 'True' : 'False');
						break;
					/* si es una cadena */
					case 'string':
						/* agregamos el parametro como texto */
						$arguments .= ',\'' . $argument . '\'';
						break;
					/* si es un numero */
					case 'integer': case 'double':
						/* agregamos el parametro como numero */
						$arguments .= ',' . $argument;
						break;
					/* si es nulo o no tiene valor */
					case 'null': default:
						/* agregamos el parametro como Null */
						$arguments .= ', Null';
						break;
				}
			}
			/* retornamos la cadena de parametros */
			return substr($arguments, 1);
		}

		private function getMethodArgs($args) {
			/* creamos un array con los parametros */
			$newArgs = Array();
			/* recorremos los parametros */
			for ($i = 1; $i < count($args); $i++)
				/* agregamos el parametro al array */
				$newArgs[] = $args[$i];
			/* retornamos los parametros */
			return $newArgs;
		}
	}
?>